/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.mlu.jdbc.simple.codegeneration;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.mlu.jdbc.simple.SimpleJdbc;
import org.mlu.jdbc.simple.utils.JdbcUtils;
import org.mlu.jdbc.simple.utils.SchemaUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Mlungisi Sincuba
 * @since 1.0
 */
public class DomainClassGenerator {

    private DataSource dataSource;
    private static final Logger logger = LoggerFactory.getLogger(DomainClassGenerator.class);

    public DomainClassGenerator(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void generateDomain(String src, String packageName, boolean mavenProject, String[] tableNames) throws SQLException, IOException {
        src = getSrcPath(src, mavenProject);
        packageName += ".domain";
        String packagePath = getPackagePath(src, packageName);

        logger.debug("packagePath is {}", packagePath);
        //List<String> tableNamesList = null;
        String[] tableNamesArray = tableNames;
        tableNamesArray = toTablesArray(tableNames, tableNamesArray);
        for (String tableName : tableNamesArray) {
            logger.debug("TableName is {}", tableName);
            
            StringBuilder sb = new StringBuilder("package ");
            sb.append(packageName).append(";\n\n");
            String className = classDeclaration(tableName, sb);

            StringBuilder toStringFormat = new StringBuilder("return String.format(\"");
            StringBuilder toStringValues = new StringBuilder();
            //Attributes
            Map<String, String> columnTypePairForTable = attributes(tableName, toStringFormat, toStringValues, sb);
            //Getters and setters
            gettersAndSetters(columnTypePairForTable, sb);
            //toString
            toStringMethod(toStringFormat, toStringValues, sb);

            String pk = SchemaUtil.getPrimaryKey(tableName, dataSource);
            if (null != pk && !"".equals(pk)) {
                //hashCode
                sb.append(getHashCodeMethod(tableName));

                //Equals
                sb.append(getEqualsMethod(className));
            }
            sb.append("\n}");

            logger.debug("sb is: {}", sb);
            saveTofile(packagePath, className, sb);
        }
    }

    private String getHashCodeMethod(String tableName) throws SQLException {
        StringBuilder sb = new StringBuilder("\n   @Override\n   public int hashCode() {\n");
        sb.append("      int hash = 0;\n      ").append("hash += (");
        String key = SchemaUtil.getPrimaryKey(tableName, dataSource);
        String propertyKey = JdbcUtils.convertUnderscoreNameToPropertyName(key);
        sb.append(propertyKey).append(" != null ? ").append(propertyKey).append(".hashCode() : 0);\n");
        sb.append("      return hash;\n   }\n");
        return sb.toString();
    }

    private String getEqualsMethod(String className) throws SQLException {
        String tableName = className.toLowerCase();
//        tableName = JdbcUtils.convertPropertyNameToUnderscoreName(className).toLowerCase();

        logger.debug("getEqualsMethod: tableName = {}", tableName);
        String key = SchemaUtil.getPrimaryKey(tableName, dataSource);
        logger.debug("getEqualsMethod: key = {}", key);
        String propertyKey = JdbcUtils.convertUnderscoreNameToPropertyName(key);
        logger.debug("getEqualsMethod: propertyKey = {}", propertyKey);
        StringBuilder sb = new StringBuilder("\n   @Override\n   public boolean equals(Object object) {\n");
        sb.append("      if (!(object instanceof ").append(className).append(")) {\n         ");
        sb.append("return false;\n      }\n       ");
        sb.append("if (getClass() != object.getClass()) {\n").append("         ");
        sb.append("return false;\n      }\n       ");
        sb.append("final ").append(className).append(" other = (").append(className).append(") object;\n      ");
        sb.append("if ((this.").append(propertyKey).append(" == null) ? (other.").append(propertyKey)
                .append(" != null) : !this.").append(propertyKey).append(".equals(other.")
                .append(propertyKey).append(")) {\n         ");
        sb.append("return false;\n      }\n      return true;\n   }");

        return sb.toString();
    }

//    private String getPrimaryKey(String tableName) throws SQLException {
//        Map<String, Boolean> primaryKeysForTable = SchemaUtil.getPrimaryKeysForTable(getDataSource().getConnection(), tableName);
//        for (Map.Entry<String, Boolean> entry : primaryKeysForTable.entrySet()) {
//            String key = entry.getKey();
//            return key;
//        }
//        return "";
//    }

    public static void main(String... args) throws SQLException, IOException {
//        String format = String.format("%d, %s, %s, %b", 4, "test", new Date(System.currentTimeMillis()), true);
//        System.out.println("format = " + format);
        SimpleJdbc jdbc = new SimpleJdbc();
        DomainClassGenerator generator = new DomainClassGenerator(jdbc.getDataSource());
        generator.generateDomain(null, "com.movirtu", true, null);
    }

    /**
     * @return the dataSource
     */
    public DataSource getDataSource() {
        return dataSource;
    }

    /**
     * @param dataSource the dataSource to set
     */
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    private String[] toTablesArray(String[] tableNames, String[] tableNamesArray) throws SQLException {
        List<String> tableNamesList;
        if (tableNames == null) {
            logger.debug("Generating classes from all tables on database...");
            tableNamesList = SchemaUtil.getTableNames(getDataSource().getConnection(), null);
            tableNamesArray = (String[]) tableNamesList.toArray(new String[tableNamesList.size()]);
        }
        return tableNamesArray;
    }

    private String getSrcPath(String src, boolean mavenProject) {
        if (src == null) {
            File srcFilePath = new File(".");
            try {
                src = srcFilePath.getCanonicalPath() + File.separator + "src";
                if (mavenProject) {
                    src += File.separator + "main" + File.separator + "java";
                }
            } catch (IOException ex) {
                logger.error("src path not found", ex);
            }
        }
        logger.debug("src = {}", src);
        return src;
    }

    private String getPackagePath(String src, String packageName) {
        //String domainSrc = src + File.separator + "domain";
        File srcFilePath = new File(src);

        if (!srcFilePath.exists()) {
            logger.debug("Created src path: {}", src);
            srcFilePath.mkdir();
        } else {
            logger.debug("Could not create src path: {}", src);
        }
        logger.debug("src = {}", src);
        if (packageName == null) {
            packageName = ".domain";
        }
        String[] dirs = packageName.split("\\.");

        String s = "";
        for (String path : dirs) {
            logger.debug("path1 {}", s);
            s += File.separator + path;
            logger.debug("path2 {}", s);
            String fullPath = src + s;
            System.out.println("fullPath: " + fullPath);

            File srcPath = new File(fullPath);
            boolean createdPath = srcPath.mkdirs();
            logger.debug("Created path: {}", createdPath);

        }
        String packagePath = packageName.replaceAll("\\.", File.separator);
        packagePath = src + File.separator + packagePath;
        logger.debug("packagePath = {}", packagePath);
        return packagePath;
    }

    private Map<String, String> attributes(String tableName, StringBuilder toStringFormat, StringBuilder toStringValues, StringBuilder sb) throws SQLException {
        //Attributes
        Map<String, String> columnTypePairForTable = SchemaUtil.getColumnTypePairForTable(getDataSource().getConnection(), tableName);
        for (Map.Entry<String, String> entry : columnTypePairForTable.entrySet()) {

            String columnName = entry.getKey();
            String javaType = entry.getValue();
            String propertyName = JdbcUtils.convertUnderscoreNameToPropertyName(columnName);
            toStringFormat.append(propertyName).append("=").append("%s, ");
            toStringValues.append(propertyName).append(", ");
            sb.append("   private ").append(javaType).append(" ").append(propertyName).append(";\n");
        }
        sb.append("\n");
        return columnTypePairForTable;
    }

    private void gettersAndSetters(Map<String, String> columnTypePairForTable, StringBuilder sb) {
        //Methods
        for (Map.Entry<String, String> entry : columnTypePairForTable.entrySet()) {
            String columnName = entry.getKey();
            String javaType = entry.getValue();
            String propertyName = JdbcUtils.convertUnderscoreNameToPropertyName(columnName);
            String getPrefix = "get";
            if ("boolean".equals(javaType)) {
                getPrefix = "is";
            }

            //Getters
            String getMethodName = getPrefix + JdbcUtils.convertTableNameToClassName(columnName) + "() {";
            sb.append("   public ").append(javaType).append(" ").append(getMethodName).append("\n");
            sb.append("      return this.").append(propertyName).append(";\n   }\n");

            //Setters
            String setMethodName = "set" + JdbcUtils.convertTableNameToClassName(columnName);
            sb.append("   public void ").append(setMethodName).append("(").append(javaType).
                    append(" ").append(propertyName).append(") {\n")
                    .append("      this.").append(propertyName).append(" = ").append(propertyName)
                    .append(";\n   }\n\n");
        }
    }

    private void toStringMethod(StringBuilder toStringFormat, StringBuilder toStringValues, StringBuilder sb) {
        //toString
        toStringFormat = toStringFormat.replace(toStringFormat.lastIndexOf(", "), toStringFormat.length(), "\", ");
        toStringValues = toStringValues.replace(toStringValues.lastIndexOf(", "), toStringValues.length(), ");\n");
        toStringFormat.append(toStringValues);
        sb.append("   @Override\n");
        sb.append("   public String toString() {\n      ").append(toStringFormat).append("   }\n");
    }

    private void saveTofile(String packagePath, String className, StringBuilder sb) throws IOException {
        //Save to file
        File packagePathFile = new File(packagePath);
        if (!packagePathFile.exists()) {
            packagePathFile.mkdir();
        }
        String path = packagePathFile.getCanonicalPath();
        logger.debug("path = {}", path);
        String javaFileName = path + File.separator + className + ".java";
        logger.debug("javaFileName = {}", javaFileName);
        File javaFile = new File(javaFileName);
        if (!javaFile.exists()) {
            javaFile.createNewFile();
        }
        BufferedWriter bw = new BufferedWriter(new FileWriter(javaFile));
        bw.write(sb.toString());
        bw.flush();
        bw.close();
    }

    private String classDeclaration(String tableName, StringBuilder sb) {
        //Class declaration
        String className = JdbcUtils.convertTableNameToClassName(tableName);
        sb.append("public class ");
        logger.debug("className is {}", className);
        sb.append(className).append(" {\n\n");
        return className;
    }
}
